<!--
 * Copyright (c) 2023 FengTaiSEC Corporation.
 * @Description:
 * @Author: 刘宇薇<liuyuwei@fengtaisec.com>
 * @Date: 2023-05-30 15:14:30
 * @LastEditTime: 2025-05-12 14:44:19
 * @LastEditors: 自己的姓名<XXXXX@fengtaisec.com>
 * @FilePath: \nw-web\src\views\simulation\scenes\manage\index.vue
 * @AmendmentRecord:
-->
<template>
  <div class="detailBox">
    <TopUtils
      :name="state.title"
      :id="ID"
      :isCopy="isCopy"
      @generatePicture="saveScene"
      @refresh="refreshTopo"
      @saveScene="saveScene(true)"
      v-if="!isSceneCreate"
    />

    <div class="screen_content">
      <!-- <el-icon class="leftIcon" @click="refreshScene" :class="{ 'rightIcon': !showLeft }">
        <div class="lefticonimg" v-if="!showLeft"></div>
        <div class="righticonimg" v-else></div>
        <DArrowRight v-if="!showLeft" />
          <DArrowLeft v-else /> -->
      <!-- </el-icon> -->

      <!-- <div
        class="left scenes_left"
        :class="{ 'scene-fold-left': !showLeft }"
        :style="{ display: showLeft ? 'block' : 'none' }"
      >
        <el-tabs
          v-model="activeTab"
          @tab-click="handleTabClick"
          style="margin: 5px"
        >
          <el-tab-pane label="靶标资源" name="component">
            <div class="resource-pane">
              <div class="group-bar">
                <div
                  v-for="(item, index) in radioGroup"
                  :key="item.key"
                  class="group-btn"
                  :class="{ active: index === designIndex }"
                  @click="changeRadio(item.key, index)"
                  :title="item.nickname"
                >
                  {{ item.nickname }}
                </div>
              </div>
              <div class="search-card">
                <el-input
                  v-model="searchWord"
                  placeholder="搜索资源名称..."
                  clearable
                  @input="searchFunc"
                  
                  :prefix-icon="Search"
                  class="custom-search-input compact-search-input"
                />
              </div>
              <el-collapse
                v-model="activeNames"
                class="resource-collapse"
                @change="handleCollapseChange"
              >
                <el-collapse-item
                  v-for="group in filteredResourceList"
                  :key="group.name"
                  :title="`${group.name}（${group.fileList.length}）`"
                  :name="group.name"
                  class="custom-collapse-item"
                >
                  <div class="resource-list">
                    <div
                      v-for="(itemChild, idx) in group.fileList"
                      :key="itemChild.name + idx"
                      class="resource-card"
                      draggable="true"
                      @dragstart="onDragStart(itemChild)"
                    >
                      <el-tooltip
                          effect="dark"
                          :content="itemChild.name"
                          placement="top-start"
                      >
                        <div>
                          <div class="icon-bg">
                            <img :src="itemChild.path" class="resource-icon" />
                          </div>

                          <div class="resource-name">
                            {{ itemChild.name }}
                          </div>
                        </div>
                      </el-tooltip>
                    </div>
                  </div>
                </el-collapse-item>
              </el-collapse>
            </div>
            <div class="screen_content-bottom">
              <div style="width: 100%; height: 100%" ref="leftMain"></div>
            </div>
          </el-tab-pane>
          <el-tab-pane label="网络资源" name="network">
            <div class="resource-pane">
              <div class="search-card">
                <el-input
                  v-model="searchWord"
                  placeholder="搜索资源名称..."
                  clearable
                  @input="searchFunc"
                  
                  :prefix-icon="Search"
                  class="custom-search-input compact-search-input"
                />
              </div>
              <el-collapse
                v-model="activeNames"
                class="resource-collapse resource-collapse-other"
                @change="handleCollapseChange"
              >
                <el-collapse-item
                  v-for="group in filteredResourceList"
                  :key="group.name"
                  :title="`${group.name}（${group.fileList.length}）`"
                  :name="group.name"
                  class="custom-collapse-item"
                >
                  <div class="resource-list">
                    <div
                      v-for="(itemChild, idx) in group.fileList"
                      :key="itemChild.name + idx"
                      class="resource-card"
                      draggable="true"
                      @dragstart="onDragStart(itemChild)"
                    >
                      <el-tooltip
                          effect="dark"
                          :content="itemChild.name"
                          placement="top-start"
                      >
                        <div>
                          <div class="icon-bg">
                            <img :src="itemChild.path" class="resource-icon" />
                          </div>

                          <div class="resource-name">
                            {{ itemChild.name }}
                          </div>
                        </div>
                      </el-tooltip>
                    </div>
                  </div>
                </el-collapse-item>
              </el-collapse>
            </div>
            <div class="screen_content-bottom">
              <div style="width: 100%; height: 100%" ref="netMain"></div>
            </div>
          </el-tab-pane>
          <el-tab-pane label="内置图形" name="builtIn">
            <div class="resource-pane">
              <el-collapse
                v-model="activeNames"
                class="resource-collapse resource-collapse-other"
                @change="handleCollapseChange"
              >
                <el-collapse-item
                  v-for="group in builtInResourceList"
                  :key="group.name"
                  :title="`${group.name}（${group.fileList.length}）`"
                  :name="group.name"
                  class="custom-collapse-item"
                >
                  <div class="resource-list">
                    <div
                      v-for="(itemChild, idx) in group.fileList"
                      :key="itemChild.name + idx"
                      class="resource-card"
                      draggable="true"
                      @dragstart="onDragStart(itemChild)"
                    >
                      <div class="icon-bg">
                        <span
                          v-if="itemChild.options.attrs?.text?.text === '文字'"
                          >{{ itemChild.options.attrs?.text?.text }}</span
                        >
                        <img
                          :src="getImage(itemChild.type)"
                          class="resource-icon"
                          v-else
                        />
                      </div>
                    </div>
                  </div>
                </el-collapse-item>
              </el-collapse>
            </div> 
            <div class="screen_content-bottom">
              <div style="width: 100%; height: 100%" ref="leftBuildMain"></div>
            </div>
          </el-tab-pane>
        </el-tabs>
      </div> -->
      <div
        class="main"
        :class="{ 'scene-fold': showLeft, 'scene-no-fold': !showLeft }"
      >
        <div
          ref="graphMain"
          class="graphMain"
          @drop="onDrop"
          @dragover.prevent
        ></div>
        <div v-if="contextGraphVisible" :style="contextOptions">
          <ul class="nodeMenu">
            <li @click="pasteFunc">
              <el-icon color="#909399">
                <component :is="DocumentCopy" />
              </el-icon>
              <span>粘贴</span>
            </li>
          </ul>
        </div>
      </div>
      <div id="minimap"></div>
      <ContextMenu />
      <Detail
        ref="nodeDetail"
        :drawer="drawer"
        :info="nodeInfo"
        :id="ID"
        :clusId="clusterID"
        @editNode="editNodeDetail"
        @closeDialog="closeFunc"
        @refreshGraph="refreshGraph"
      ></Detail>
      <ToolExecute
        ref="nodeDetail"
        :toolExecuteDrawer="toolExecuteDrawer"
        :info="nodeInfo"
        :id="ID"
        :clusId="clusterID"
        @closeDialog="closeToolExecuteFunc"
        @refreshGraph="refreshGraph"
      ></ToolExecute>
      <TrafficReplay
        ref="nodeDetail"
        :trafficDrawer="trafficDrawer"
        :info="nodeInfo"
        :id="ID"
        :clusId="clusterID"
        @closeDialog="closeTrafficFunc"
        @refreshGraph="refreshGraph"
      ></TrafficReplay>
      <Virtual
        ref="virtualComponent"
        type="scene"
        :dialogList="dialogList"
        @closeDialog="closeVirtualFunc"
        @topDialog="topDialog"
      ></Virtual>
      <Docker
        ref="dockerComponent"
        :dialogList="dockerDialogList"
        @topDialog="topDockerDialog"
        @closeDialog="closeDockerFunc"
      ></Docker>
      <DockerLog
        ref="dockerLogComponent"
        :dialogList="dockerLogList"
        @topDialog="topDockerLogDialog"
        @closeDialog="closeDockerLogFunc"
      ></DockerLog>
      <Snapshot :dialogList="dialogList"></Snapshot>
      <SnapshotScene :dialogList="dialogList"></SnapshotScene>
      <AttackPath />
      <el-dialog
        v-model="materialInterfaceShow"
        title="虚实互联配置"
        width="500"
        @close="unmatchMaterialInterface()"
      >
        <div>
          <el-form label-position="right" label-width="160px" class="mx-2 mt-1">
            <el-form-item
              label="选择实物设备端口:"
            >
              <el-select
                filterable
                clearable
                v-model="materialInterfaceID"
                @change="changeMaterialInterface"
              >
                <el-option
                  v-for="item in materialInterfaceList"
                  :key="item.ID"
                  :label="item.name"
                  :value="item.ID"
                ></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="输入虚拟机IP:">
              <el-input
                v-model="materialInterfaceConVirIP"
                clearable
                placeholder="输入虚拟机端口IP"
              />
            </el-form-item>
            <div v-if="choiceMaterialType === 2">
              <el-form-item label="输入虚拟机端口网关:">
                <el-input
                  v-model="materialInterfaceConVirWg"
                  clearable
                  placeholder="输入虚拟机端口网关"
                />
              </el-form-item>
            </div>
          </el-form>
        </div>

        <template #footer>
          <div class="dialog-footer">
            <el-button @click="unmatchMaterialInterface()">取消</el-button>
            <el-button type="primary" @click="matchMaterialInterface()">
              确认
            </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
  </div>
</template>
<script lang="ts" setup>
import {
  onMounted,
  onUnmounted,
  reactive,
  ref,
  computed,
  watch,
} from 'vue';
import { DocumentCopy, Search } from '@element-plus/icons-vue';
import eventBus from 'vue3-eventbus';
import type { Graph } from '@antv/x6';
import { sceneBuiltBaseNode } from './service/config';
import { useRouter } from 'vue-router';
import Detail from './children/detail.vue';
import TopUtils from './children/topUtils.vue';
import ContextMenu from './children/contextMenu.vue';
import Snapshot from './children/snapshot.vue';
import SnapshotScene from './children/snapshotScene.vue';
import AttackPath from './children/attackPath.vue';
import Virtual from '@/components/console/virtual.vue';
import Docker from '@/components/console/docker.vue';
import DockerLog from '@/components/console/dockerLog.vue';
import { simulationCover } from './defaultConfig';
import {
  initGraph,
  initNode,
  initStencil,
} from './service/initScene';
import {
  getComponent,
  editorNode,
  editorLink,
  deleteLink,
  deleteNode,
  editorPutNode,
  editorPutLink,
  editSceneList,
  getVirtual,
  editScene,
  transferScene,
  getToolType,
  getToolList,
  uploadMyGraphicsFile,
  getByID,
  getMaterialLinkCheck,
  getInstanceLinkOption,
  getDescFunc,
  setDescFunc,
  getComponentType,
} from '@/api/topo/topo.js';
import { getVulnEnvironmentList } from '@/api/topo/vulnEnvironment';
import { dataURLtoFile, categoryMapList, getRandomIP } from '@/utils/common';
import { showPorts } from './service/utils';
import { ElMessage } from 'element-plus/es';
import { emitter } from '@/utils/bus';
import { deepCopy, isTagName } from 'windicss/utils';
import {
  showContext,
  refreshGraph,
} from './service/vueService';
import { allCount } from '@/layout/notification/service/service';
import TrafficReplay from './children/trafficReplay.vue';
import ToolExecute from './children/debugTool.vue';
import { useGraphStore } from '@/pinia/modules/graph';
const graphStore = useGraphStore();

const router = useRouter();
const ID = ref(null);
ID.value = router.currentRoute.value.query.id;
const NAME = router.currentRoute.value.query.name;
let clusterID = ref(0);
let isCopy = ref('');
let sceneDetail = ref({});
const activeTab = ref('component');
let tabMemoryArr = [];
const showLeft = ref(true);
//容器控制台
const dockerDialogList = ref([]);
const dockerComponent = ref(null);
// 容器日志
const dockerLogList = ref([]);
const dockerLogComponent = ref(null);
// 主画布实例
const graphMain = ref(null);
// 保存拓扑实例
let graph: Graph = null;
// 保存左边栏拓扑节点实例
let stencil = null;
let stencilNet = null;
let stencilBuildIn = null;
let stencilTool = null;

// 侧边栏拖拽实例
const leftMain = ref(null);
const leftBuildMain = ref(null);
const leftToolMain = ref(null);
const netMain = ref(null);
//详情框
const drawer = ref(null);
//流量详情框
const trafficDrawer = ref(null);
//工具执行
const toolExecuteDrawer = ref(null);
const toolListData = ref([]);
//组件列表
const compList = ref([]);
const state = reactive({
  // 保存 除内置和我的图形 节点信息
  image: [],
  //  设置节点分组类型
  groups: [],
  // 拓扑名称
  title: NAME,
});

// hiddenTag 避免鼠标离开时，判断连接的接口还没有完成，导致连线完成后， 程序还去隐藏之前接口返回的数据
// 导致连线后 不能连接的组件消失
const hiddenTag = ref(true);
const nodeInfo = ref({});
const radioGroup = ref([]);
//虚拟机控制台
const dialogList = ref([]);
const virtualComponent = ref(null);
const nodeDetail = ref(null);
//画布粘贴
const contextGraphVisible = ref(false);
const contextOptions = ref({});
// 单选框
const componentClassifyType = ref();
const designIndex = ref(-1);
const designIndexLast = ref(-1);

// 查询输入
const searchWord = ref('');
// 默认展开所有分组
const activeNames = ref([]);

// 生成封面
const isSceneCreate = ref(false);

const getImage = arg => {
  if (arg == 'circle') {
    return new URL('./image/circle.png', import.meta.url).href;
  } else if (arg == 'rect') {
    return new URL('./image/rect.png', import.meta.url).href;
  } else if (arg == 'polygon') {
    return new URL('./image/polygon.png', import.meta.url).href;
  } else if (arg == 'ellipse') {
    return new URL('./image/ellipse.png', import.meta.url).href;
  } else if (arg == 'line') {
    return new URL('./image/line.svg', import.meta.url).href;
  } else {
    return new URL('./image/square.png', import.meta.url).href;
  }
};

// ========== 多tab分组展开状态独立维护 ==========
const tabActiveNames = reactive({
  component: [],
  network: [],
  builtIn: [],
  tool: [],
});

function getAllGroupNames(list) {
  return list.map(group => group.name);
}

// el-collapse折叠/展开事件
function handleCollapseChange(names) {
  tabActiveNames[activeTab.value] = names;
  activeNames.value = names;
}

// 资源数据源，直接用state.image
// 查询和分类过滤后的资源列表
const filteredResourceList = computed(() => {
  let list = state.image || [];
  if (searchWord.value) {
    // 只保留fileList中有匹配项的分组
    list = list
      .map(group => ({
        ...group,
        fileList: group.fileList.filter(item =>
          item.name.includes(searchWord.value),
        ),
      }))
      .filter(group => group.fileList.length > 0);
  }
  return list;
});

// 新增：监听 filteredResourceList 变化，更新 tabActiveNames 和 activeNames
watch(
  () => filteredResourceList.value,
  list => {
    tabActiveNames[activeTab.value] = getAllGroupNames(list);
    activeNames.value = tabActiveNames[activeTab.value];
  },
  { immediate: true },
);

// 内置图形tab专用资源列表
const builtInResourceList = computed(() => {
  let list = sceneBuiltBaseNode.map((item, idx) => ({
    name: '',
    path: '', // 可自定义icon路径
    options: {
      ...item,
      category: 5,
    },
  }));
  // 支持搜索
  if (searchWord.value) {
    list = list.filter(item => item.name.includes(searchWord.value));
  }

  return [
    {
      name: '内置图形',
      fileList: list,
    },
  ];
});

const pasteFunc = () => {
  const clipBoard = graph.isClipboardEmpty();
  if (!clipBoard) {
    graph.paste();
  } else {
    ElMessage.warning(`无内容!`);
  }
  hideDetail();
};
const hideDetail = () => {
  contextGraphVisible.value = false;
};
const graphContext = arg => {
  contextOptions.value = {
    position: 'absolute',
    left: showLeft.value
      ? arg.e.clientX - 300 + 'px'
      : arg.e.clientX - 250 + 'px',
    top: arg.e.clientY - 190 + 'px',
    zIndex: 1000,
    width: '110px',
    // background: '#fff',
    // padding: '10px 20px',
    // boxShadow: '0px 0px 12px rgba(0, 0, 0, 0.12)',
    // border: '1px solid #e4e7ed',
    // borderRadius: '2px',
  };
  contextGraphVisible.value = true;
};
// 监听websocket消息通知
const taskFunc = data => {
  const { msg } = data;
  const info = msg.update;
  allCount.value = msg.count;
  if (info) {
    let mapList = sessionStorage.getItem('mapList');
    mapList = JSON.parse(mapList);
    if (mapList && mapList[ID.value] == info.ID) {
      if (info.status == 3) {
        drawer.value = false;
        refreshGraph(ID.value, graph);
      } else {
        delete mapList[ID.value];
        sessionStorage.setItem('mapList', JSON.stringify(mapList));
      }
    }
    let nodeList = sessionStorage.getItem('nodeList');
    nodeList = JSON.parse(nodeList);
    if (nodeList[info.ID]) {
      let options = nodeList[info.ID].options;
      if (info.status == 3) {
        if (
          options.indexOf('start') !== -1 ||
          options.indexOf('stop') !== -1 ||
          options.indexOf('reset') !== -1
        ) {
          changeNodeStatus(info.ID, nodeList[info.ID].ID, options);
        } else if (options == 'acl') {
          if (nodeDetail.value.getMyNodeList) {
            nodeDetail.value.getMyNodeList();
          }
          delete nodeList[info.ID];
          sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
        }
      } else {
        delete nodeList[info.ID];
        sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
      }
    }
  }
};
// 监听子组件方法刷新画布
const refreshTopo = () => {
  refreshGraph(ID.value, graph);
};

const setTaskId = async (taskID, id, flag) => {
  let mapList = sessionStorage.getItem('nodeList') || '{}';
  mapList = JSON.parse(mapList);
  mapList[taskID] = { ID: id, options: flag };
  sessionStorage.setItem('nodeList', JSON.stringify(mapList));
};

onMounted(async () => {
  await initContainer();
  //监听增加节点/连线
  eventBus.on('addScreenNode', changeGraph);
  //监听移除节点/连线
  eventBus.on('removeScreenNode', removeCeil);
  //监听修改节点/连线
  eventBus.on('changePutGraph', changePutGraph);
  //展开详情编辑框
  eventBus.on('editNode', editNodeFunc);
  //流量重放
  eventBus.on('trafficReplay', trafficReplayFunc);
  // 工具执行
  eventBus.on('toolExecute', toolExecuteFunc);
  //控制台
  eventBus.on('changeVirtual', changeVirtual);
  // 容器日志
  eventBus.on('containerLogs', containerLogs);
  //编辑开启状态
  eventBus.on('openFunc', openFunc);
  //关闭详情
  eventBus.on('hideDetail', closeFunc);
  // 监听websocket消息通知
  emitter.on('Task', taskFunc);
  //监听右键画布
  eventBus.on('graphContext', graphContext);
  eventBus.on('hideDetail', hideDetail);

  eventBus.on('changeText', changeText);
  // 监听鼠标按下边
  eventBus.on('mouseDownEdge', mouseDownEdge);
  // 监听鼠标离开边
  eventBus.on('mouseLeaveEdge', mouseLeaveEdge);

  sessionStorage.setItem('nodeList', '{}');
  // getComponentTypeList();

  // 监听主题管理改变
  emitter.on('changeTheme', initContainer);
});

const getComponentTypeList = async () => {
  getComponentType().then(res => {
    radioGroup.value = res.data;
    // if (radioGroup.value.length > 0) {
    //   componentClassifyType.value = radioGroup.value[0]?.key;
    //   changeRadio(componentClassifyType.value, -1);
    // }
  });
};

onUnmounted(async () => {
  eventBus.off('addScreenNode', changeGraph);
  eventBus.off('editNode', editNodeFunc);
  eventBus.off('removeScreenNode', removeCeil);
  eventBus.off('changePutGraph', changePutGraph);
  eventBus.off('changeVirtual', changeVirtual);
  eventBus.off('containerLogs', containerLogs);
  eventBus.off('openFunc', openFunc);
  eventBus.off('trafficReplay', trafficReplayFunc);
  eventBus.off('toolExecute', toolExecuteFunc);
  eventBus.off('hideDetail', closeFunc);
  emitter.off('Task', taskFunc);
  eventBus.off('graphContext', graphContext);
  eventBus.off('hideDetail', hideDetail);
  eventBus.off('changeText', changeText);
  eventBus.off('mouseDownEdge', mouseDownEdge);
  eventBus.off('mouseLeaveEdge', mouseLeaveEdge);
  emitter.off('changeTheme', initContainer);
  showContext(false);
});

let hiddenInstances = [];
const mouseDownEdge = async arg => {
  hiddenTag.value = true;
  // 区分连线和点击连接桩，如果是连线那么target应该有cell，连接桩的话是没有cell属性的
  if (arg.view.cell.target.cell) {
    hiddenTag.value = false;
    return;
  }
  let res = await getInstanceLinkOption(ID.value, arg.view.cell.source.cell);
  if (!(res instanceof Error)) {
    if (res.data) {
      // 避免该接口再连线完成后触发，导致连线后隐藏组件
      if (hiddenTag.value) {
        hiddenInstances = res.data;
        res.data.forEach(item => {
          let node = graph.getCellById(item);
          node.setVisible(false);
        });
      }
    }
  }
  hiddenTag.value = false;
};

const mouseLeaveEdge = async arg => {
  hiddenInstances.forEach(item => {
    let node = graph.getCellById(item);
    node.setVisible(true);
  });
  hiddenTag.value = false;
};
const changeText = async arg => {
  let cell = graph.getCellById(arg.id);
  cell.attr('text/text', arg.text);
  await setDescFunc({
    id: ID.value,
    uuid: arg.id,
    name: arg.text,
    isEasyPut: true,
  });
};
const editNodeDetail = async (arg, image) => {
  let cell = graph.getCellById(arg.id);
  cell.attr('text/text', arg.attrs.text.text);
  cell.attr('image/xlink:href', arg.attrs.image['xlink:href']);
  if (image) {
    cell.attr('.rankCenter/xlink:href', simulationCover(image));
  }
};
const openFunc = async param => {
  let arg = param.cell;
  let flag = param.flag;
  let res = await editScene(ID.value, arg.id, { operation: flag });
  if (!(res instanceof Error)) {
    await setTaskId(res.data.taskID, arg.id, flag);
  }
};
const changeNodeStatus = async (taskID, id, flag) => {
  let cell = graph.getCellById(id);
  cell.attr(
    '.rank/fill',
    flag === 'stop'
      ? '#F56C6C'
      : flag === 'start' || flag === 'restart'
      ? '#67C23A'
      : '#909399',
  );
  let nodeList = sessionStorage.getItem('nodeList');
  nodeList = JSON.parse(nodeList);
  delete nodeList[taskID];
  sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
};

/**
 * 设置初始化弹窗zIndex
 * @param curData
 */
const setInitDialogZIndex = curData => {
  // 初始化控制台zIndex
  let containerArr = [
      ...dialogList.value,
      ...dockerDialogList.value,
      ...dockerLogList.value,
    ],
    currentZIndex = 10000,
    maxZIndex = 10000;
  // 获取最大的zIndex
  if (containerArr.length > 0) {
    maxZIndex = Math.max(...containerArr.map(item => item.zIndex || 0));
  }

  // 设置新的zIndex
  currentZIndex = maxZIndex + 1;
  curData.zIndex = currentZIndex;
};

//控制台
const changeVirtual = async arg => {
  if (arg.category == 1) {
    let index = dialogList.value.findIndex(item => {
      return item.RID == arg.id;
    });
    getVirtual(ID.value, arg.id, { read: false, record: false }).then(
      async res => {
        if (!(res instanceof Error)) {
          const {
            data: { token, uri },
          } = res;

          let virtualData = {
            token: token,
            uri: uri,
            id: ID.value,
            RID: arg.id,
            name: arg.name,
            read: false,
            record: false,
            zIndex: 10000,
          };

          setInitDialogZIndex(virtualData);

          if (index == -1) {
            dialogList.value.push(virtualData);
            virtualComponent.value?.openFunc(virtualData);
          } else {
            topDialog(virtualData);
          }
        }
      },
    );
  } else if (arg.category == 2) {
    let index = dockerDialogList.value.findIndex(item => {
      return item.RID == arg.id;
    });
    let dockerData = {
      id: ID.value,
      RID: arg.id,
      name: arg.name,
      zIndex: 10000,
    };

    setInitDialogZIndex(dockerData);

    if (index == -1) {
      dockerDialogList.value.push(dockerData);
      dockerComponent.value?.openFunc(dockerData, false);
    } else {
      topDockerDialog(dockerData);
    }
  }
};

// 容器日志
const containerLogs = async arg => {
  let index = dockerLogList.value.findIndex(item => {
    return item.RID == arg.id;
  });
  let nodeInfo = await getDescFunc({ id: ID.value, uuid: arg.id });
  if (nodeInfo.data.dockerID == 0) {
    ElMessage.warning('获取容器id失败');
    return;
  }
  let dockerData = {
    id: ID.value,
    RID: arg.id,
    name: nodeInfo.data.name,
    zIndex: 10000,
    data: {
      ID: nodeInfo.data.dockerID,
    },
  };

  setInitDialogZIndex(dockerData);

  if (index == -1) {
    dockerLogList.value.push(dockerData);
  } else {
    topDockerLogDialog(dockerData);
  }
};

/**
 * @description 初始化画布
 */
const initContainer = async () => {
  if (!graphMain.value) return;

  graph = initGraph(graphMain.value);
  //初始化节点/连线
  sceneDetail = await refreshGraph(ID.value, graph);
  clusterID.value = sceneDetail.value.clusterID;
  isCopy.value = sceneDetail.value.isCopy;
  // await initComponentsMenu();

  // 存储tab
  tabMemoryArr.push('component');

  // 获取初始化边信息
  graphStore.edges = JSON.stringify(graph.getEdges());
};
/**
 * @description 拓扑图片
 */
// 生成当前拓扑信息
const saveScene = async transfer => {

  isSceneCreate.value = true;
  graph.toPNG(
    async dataUri => {

      const file = dataURLtoFile(dataUri, 'topology');
      const formData = new FormData();
      formData.append('file', file);
      const resUpload = await uploadMyGraphicsFile(formData);
      if (!(resUpload instanceof Error)) {
        await editSceneList({
          id: ID.value,
          name: NAME,
          cover: resUpload.data?.file_path,
        });
        if (!transfer) {
          ElMessage.success(`生成成功!`);
        }
        if (transfer === true) {
          let resDetail = await transferScene(ID.value);
          if (!(resDetail instanceof Error)) {
            ElMessage.success(`操作成功!`);
          }
        }

        isSceneCreate.value = false;
      } else {
        isSceneCreate.value = false;
      }
    },
    { quality: 1, padding: 10, width: 240, height: 240 },
  );
};
/**
 * @description 初始化侧边栏
 */
const initComponentsMenu = async () => {
  const res = await getComponent({
    category: componentClassifyType.value,
    cluster: clusterID.value,
  });
  if (!(res instanceof Error)) {
    compList.value = [].concat(res?.data || []);
    let imageArr = [];
    for (let i = 0; i < compList.value.length; i++) {
      let item = compList.value[i];
      let fileList = [];
      for (let j = 0; j < item.data.length; j++) {
        let itemChild = item.data[j];
        itemChild['componentCategory'] = itemChild.category;
        itemChild['category'] = categoryMapList[itemChild.kindName];
        fileList.push({
          name: itemChild.name,
          path: simulationCover(itemChild.icon.path),
          options: itemChild,
        });
      }
      imageArr.push({
        name: item.name,
        fileList: fileList || [],
      });
    }
    state.image = imageArr;
    state.groups = [].concat(f(compList.value));
    stencil = initStencil(graph, state.groups, 4);
    registerNode(state.image, stencil);
    leftMain.value.appendChild(stencil.container);

    function f(arr) {
      return (
        arr?.map(el => ({
          name: el.name,
          title: el.name,
          collapsable: true,
        })) || []
      );
    }
  }
};

const initNetMenu = async id => {
  try {
    let res = await getByID(id.value);
    compList.value = res?.data || {};
    let imageArr = [];
    let item = [];
    compList.value['name'] = '集群网络';
    item.push(compList.value);
    imageArr.push({
      name: '沙箱网络',
      fileList: [
        {
          name: '交换设备',
          path: simulationCover('/img/freeBridgeDefault.png'),
          options: {
            category: 6,
            name: '交换设备',
            kindName: '沙箱网络',
          },
        },
      ],
    });
    imageArr.push({
      name: compList.value['name'],
      fileList:
        compList.value['networks']?.map(itemChild => ({
          name: itemChild.name,
          path: simulationCover('/img/clusterNetworkDefault.png'),
          options: Object.assign({
            kindName: '测试集群',
            category: 4,
            name: itemChild.name,
            ID: itemChild.ID,
          }),
        })) || [],
    });

    imageArr.push({
      name: 'VPC网络',
      fileList: [
        {
          name: '路由设备',
          path: simulationCover('/img/路由器.svg'),
          options: {
            category: 10,
            name: '路由设备',
            kindName: 'VPC网络',
          },
        },
      ],
    });
    state.image = imageArr;
    state.groups = f(item).concat([
      { name: '沙箱网络', title: '沙箱网络', collapsable: true },
      { name: 'VPC网络', title: 'VPC网络', collapsable: true },
    ]);

    let vpcArr = [
      {
        name: 'VPC网络',
        fileList: [
          {
            name: '交换设备',
            path: simulationCover('/img/freeBridgeDefault.png'),
            options: {
              category: 6,
              name: '交换设备',
              kindName: '沙箱网络',
            },
          },
        ],
      },
    ];

    stencilNet = initStencil(graph, state.groups, 4);
    registerNode(state.image, stencilNet);
    netMain.value.appendChild(stencilNet.container);

    function f(arr) {
      return (
        arr?.map(el => ({
          name: el.name,
          title: el.name,
          collapsable: true,
        })) || []
      );
    }
  } catch (error) {}
};

const handleTabClick = async val => {
  let name = val.props.name;

  tabMemoryArr.push(name);
  if (name == 'component') {
    await initComponentsMenu();
  }
  if (name == 'network') {
    await initNetMenu(clusterID);
  }
  if (name == 'builtIn') {
    await openBuildIn();

    tabActiveNames[activeTab.value] = getAllGroupNames(
      builtInResourceList.value,
    );
    activeNames.value = tabActiveNames[activeTab.value];
  }
  if (name == 'tool') {
    await initTool();
  }
};

const initTool = async () => {
  state.groups = [
    {
      name: 'tool',
      title: '工具资源',
      collapsable: true,
      fileList: [],
    },
  ];
  // 查询列表数据
  await getToolList({ page: 1, pageSize: 9999 }).then(res => {
    toolListData.value = res.data?.list;
  });
  for (let t in toolListData.value) {
    let _img = '/img/tool_vuln_scan.svg';
    switch (toolListData.value[t].type) {
      case 1:
        _img = '/img/tool_vuln_scan.svg';
        break;
      case 2:
        _img = '/img/tool_blast.svg';
        break;
      case 4:
        _img = '/img/tool_poc.svg';
        break;
      case 5:
        _img = '/img/tool_traffic_monitor.svg';
        break;
    }
    // 将数据填充到分类中
    const options = toolListData.value[t];
    options.category = 9;
    options.name = toolListData.value[t].name;
    options.mirrorImage = 1;
    options.componentCategory = toolListData.value[t].type.toString();
    options.kindName = '工具资源';
    state.groups[0].fileList.push({
      name: toolListData.value[t].name,
      path: simulationCover(_img),
      options: options,
    });
  }
  stencilTool = initStencil(graph, state.groups, 4);
  registerNode(state.groups, stencilTool);
  leftToolMain.value.appendChild(stencilTool.container);
};

const openBuildIn = async () => {
  state.groups = [
    {
      name: 'builtIn',
      title: '内置图形',
      collapsable: true,
    },
  ];
  stencilBuildIn = initStencil(graph, state.groups, 4);
  stencilBuildIn.load(sceneBuiltBaseNode, 'builtIn');
  leftBuildMain.value.appendChild(stencilBuildIn.container);
};
//筛选组件
const changeRadio = async (val, index) => {
  designIndexLast.value = designIndex.value;
  componentClassifyType.value = val;
  designIndex.value = index;

  if (designIndexLast.value === designIndex.value) {
    designIndex.value = -1;
    componentClassifyType.value = '';
  }

  leftMain.value.removeChild(stencil.container);
  if (val != 8) {
    await initComponentsMenu();
  }
};

//修改topo详情
const changeGraph = async para => {
  let options =
    para.value.attrs.image && para.value.attrs.image.options
      ? para.value.attrs.image.options
      : null;

  // 第一次拖出组件并修改后复制粘贴，name不变
  let val;
  if (para.type.indexOf('node') != -1) {
    options.name = para.value.attrs.text.text;
    val = options;
  } else {
    val = para.value;
  }

  if (para.type.indexOf('node') != -1) {
    // 从图实例中获取正确的Cell对象
    const cellNode = graph.getCellById(para.value.id);
    if (cellNode && typeof cellNode.hasPorts === 'function') {
      showPorts(cellNode, false);
    }
    
    // 使用正确的Cell对象获取JSON数据
    let newObj;
    if (cellNode && typeof cellNode.toJSON === 'function') {
      newObj = cellNode.toJSON();
    } else if (para.value && typeof para.value.toJSON === 'function') {
      newObj = para.value.toJSON();
    } else {
      console.error('无法获取有效的Cell对象进行JSON转换');
      return;
    }
    // // 判断是否是矩形框
    // if (newObj.zIndex === -1) {
    //   newObj.shape = '';
    // }

    console.log('==>', newObj);
    
    // 使用正确的Cell对象获取属性，优先使用cellNode，备用newObj
    const currentCell = cellNode || para.value;
    const cellAttrs = currentCell.attrs || newObj.attrs || {};
    const imageOptions = cellAttrs.image?.options || {};
    const textAttrs = cellAttrs.text || {};
    
    let arg = options
      ? {
          type: imageOptions.type || '',
          disk: imageOptions.disk || 0,
          cores: imageOptions.cpu || 0,
          memory: imageOptions.mem || 0,
          startConfig: imageOptions.startConfig || '',
          name: val.name || textAttrs.text || '',
          cpuModel: imageOptions.cpuModel || '',
          videoType: imageOptions.videoType || '',
          busType: imageOptions.busType || '',
          isTablet: imageOptions.isTablet || false,
          category: imageOptions.category || 0,
          componentCategory: imageOptions.componentCategory || '',
          id: imageOptions.id || 0,
          clusterNetworkID: val.ID || imageOptions.clusterNetworkID || 0,
          baseImagePath: val.path || imageOptions.baseImagePath || '',
          baseImageSystemType: val.systemType || imageOptions.baseImageSystemType || 0,
          baseImageName: imageOptions.name || '',
          baseImageType: (val.equipment && val.equipment.type) || imageOptions.baseImageType || 0,
          mirrorImage: val.mirror_image || imageOptions.mirrorImage || 0,
          baseImageUsername: (val.equipment && val.equipment.userName) || imageOptions.baseImageUsername || '',
          baseImagePassword: (val.equipment && val.equipment.passWord) || imageOptions.baseImagePassword || '',
          baseImageIpAddr: (val.equipment && val.equipment.ipAddr) || imageOptions.baseImageIpAddr || '',
          baseImagePort: (val.equipment && val.equipment.port) || imageOptions.baseImagePort || '',
          instanceData: {
            id: newObj.id,
            portInfo: newObj.ports.items,
            height: newObj.size.height,
            width: newObj.size.width,
            x: newObj.position.x,
            y: newObj.position.y,
            zIndex: newObj.zIndex,
            shape: newObj.shape,
          },
          toolData: imageOptions.config,
          toolUUID: imageOptions.uuid,
          image: cellAttrs.image
            ? cellAttrs.image['xlink:href'] === '/'
              ? '1'
              : cellAttrs.image['xlink:href']
            : '',
        }
      : {};

    let res = await editorNode(ID.value, arg);
    if (!(res instanceof Error)) {
      let cell = graph.getCellById(newObj.id);
      cell.attr(
        '.rankCenter/xlink:href',
        simulationCover(res.data.computerImage),
      );
      if (
        imageOptions.category === 7 ||
        imageOptions.category === 8
      ) {
        if (res.data.isRunning) {
          cell.attr('.rank/fill', '#67C23A');
        } else {
          cell.attr('.rank/fill', '#F56C6C');
        }
      }
    } else {
      graph?.removeCell(currentCell, { slient: true });
    }
  } else {
    let para1 = {
      id: val.id,
      source: val.source,
      target: val.target,
      zIndex: val.zIndex,
    };
    let arr = [
      {
        markup: [
          {
            tagName: 'ellipse',
            selector: 'labelBody',
          },
          {
            tagName: 'text',
            selector: 'labelText',
          },
        ],
        attrs: {
          labelText: {
            text: '',
            fill: '#474747',
            textAnchor: 'middle',
            textVerticalAnchor: 'middle',
          },
          labelBody: {
            ref: 'labelText',
            refRx: '60%',
            refRy: '60%',
            stroke: '#5F95FF', // 第一个
            fill: '#fff',
            strokeWidth: 1,
          },
        },
        position: { distance: 0.1 },
      },
      {
        markup: [
          {
            tagName: 'ellipse',
            selector: 'labelBody',
          },
          {
            tagName: 'text',
            selector: 'labelText',
          },
        ],
        attrs: {
          labelText: {
            text: '',
            fill: '#474747',
            textAnchor: 'middle',
            textVerticalAnchor: 'middle',
          },
          labelBody: {
            ref: 'labelText',
            refRx: '60%',
            refRy: '60%',
            stroke: '#5F95FF', // 第二个
            fill: '#fff',
            strokeWidth: 1,
          },
        },
        position: { distance: 0.9 },
      },
    ];
    let sourceCell = graph.getCellById(para1.source.cell);
    let targetCell = graph.getCellById(para1.target.cell);
    if (
      sourceCell.attrs.image.options.category == 7 ||
      targetCell.attrs.image.options.category == 7
    ) {
      const materialID =
        sourceCell.attrs.image.options.category == 7
          ? para1.source.cell
          : para1.target.cell;
      let nodeInfo = await getDescFunc({ id: ID.value, uuid: materialID });

      materialInterfaceData.value = {
        sourceCell,
        targetCell,
        para1,
        para,
      };

      if (!(nodeInfo instanceof Error)) {
        if (nodeInfo.data.localMaterial?.linkEquipment?.type === 1) {
          choiceMaterialType.value = 1;
          materialInterfaceList.value = nodeInfo.data.localMaterial.interfaces;
        } else if (nodeInfo.data.localMaterial?.linkEquipment?.type === 2) {
          choiceMaterialType.value = 2;
          materialInterfaceList.value = nodeInfo.data.localMaterial.interfaces;
          materialEqInterfaceList.value =
            nodeInfo.data.localMaterial.linkEquipment.interfaces;
          await getMaterialInterface(sourceCell.id, targetCell.id);
        } else {
          ElMessage.warning('所连物理设备的接入设备信息获取失败');
          unmatchMaterialInterface();
          return;
        }
      }
      materialInterfaceShow.value = true;
      return;
    } else {
      let res = await editorLink(ID.value, para1);
      if (!(res instanceof Error)) {
        let data = res.data;
        let cell = graph.getCellById(para1.id);
        arr[0].attrs.labelText.text = data.sourceInstancePortName;
        arr[1].attrs.labelText.text = data.destInstancePortName;
        cell.setLabels(arr);
        sourceCell.attr(
          '.rankCenter/xlink:href',
          simulationCover(data.sourceInstanceImage),
        );
        targetCell.attr(
          '.rankCenter/xlink:href',
          simulationCover(data.destInstanceImage),
        );
      } else {
        graph?.removeCell(para.value, { slient: true });
      }
      // 极特殊的情况下，无法自动触发鼠标离开事件，导致无法显示被隐藏的组件，这里，确保连线成功后手动触发一次
      hiddenTag.value = false;
      await mouseLeaveEdge(null);
    }
  }
};

//物理设备连接弹窗
const materialInterfaceShow = ref(false);
const materialInterfaceID = ref(null);
const materialInterfaceConVirIP = ref(null);
const materialInterfaceConVirWg = ref(null);
const materialInterfaceData = ref({});
const materialInterfaceList = ref([]);
const materialEqInterfaceList = ref([]);
const choiceMaterialType = ref(0);

const changeMaterialInterface = async item => {
  if (choiceMaterialType.value == 1) {
    materialInterfaceConVirIP.value = '';
    // 计算链路设备连接类型IP
    for (let i = 0; i < materialInterfaceList.value.length; i++) {
      if (item === materialInterfaceList.value[i].ID) {
        const _ip = getRandomIP(materialInterfaceList.value[i].host);
        materialInterfaceConVirIP.value = _ip.randomIp + '/' + _ip.mask;
      }
    }
    return;
  }
};

const getMaterialInterface = async (sID, tID) => {
  // 计算路由设备连接类型IP
  getMaterialLinkCheck({ sourceUUID: sID, targetUUID: tID }).then(res => {
    if (!(res instanceof Error)) {
      const _ip = getRandomIP(res.data?.host);
      materialInterfaceConVirIP.value = _ip.randomIp + '/' + _ip.mask;
      materialInterfaceConVirWg.value = '';
    }
  });
};

const matchMaterialInterface = async () => {
  let { sourceCell, targetCell, para1, para } = materialInterfaceData.value;
  let arr = [
    {
      markup: [
        {
          tagName: 'ellipse',
          selector: 'labelBody',
        },
        {
          tagName: 'text',
          selector: 'labelText',
        },
      ],
      attrs: {
        labelText: {
          text: '',
          fill: '#474747',
          textAnchor: 'middle',
          textVerticalAnchor: 'middle',
        },
        labelBody: {
          ref: 'labelText',
          refRx: '60%',
          refRy: '60%',
          stroke: '#5F95FF', // 第一个
          fill: '#fff',
          strokeWidth: 1,
        },
      },
      position: { distance: 0.1 },
    },
    {
      markup: [
        {
          tagName: 'ellipse',
          selector: 'labelBody',
        },
        {
          tagName: 'text',
          selector: 'labelText',
        },
      ],
      attrs: {
        labelText: {
          text: '',
          fill: '#474747',
          textAnchor: 'middle',
          textVerticalAnchor: 'middle',
        },
        labelBody: {
          ref: 'labelText',
          refRx: '60%',
          refRy: '60%',
          stroke: '#5F95FF', // 第二个
          fill: '#fff',
          strokeWidth: 1,
        },
      },
      position: { distance: 0.9 },
    },
  ];
  para1 = Object.assign(para1, {
    materialInterfaceID: materialInterfaceID.value,
    materialInstanceIP: materialInterfaceConVirIP.value,
    materialInstanceGW: materialInterfaceConVirWg.value,
  });
  let res = await editorLink(ID.value, para1);
  if (!(res instanceof Error)) {
    let data = res.data;
    let cell = graph.getCellById(para1.id);
    arr[0].attrs.labelText.text = data.sourceInstancePortName;
    arr[1].attrs.labelText.text = data.destInstancePortName;
    cell.setLabels(arr);
    sourceCell.attr(
      '.rankCenter/xlink:href',
      simulationCover(data.sourceInstanceImage),
    );
    targetCell.attr(
      '.rankCenter/xlink:href',
      simulationCover(data.destInstanceImage),
    );
  } else {
    graph?.removeCell(para.value, { slient: true });
  }
  // 极特殊的情况下，无法自动触发鼠标离开事件，导致无法显示被隐藏的组件，这里，确保连线成功后手动触发一次
  materialInterfaceShow.value = false;
  materialInterfaceID.value = null;
  materialInterfaceData.value = {};
  materialInterfaceList.value = [];
  materialEqInterfaceList.value = [];
  materialInterfaceConVirWg.value = null;
  materialInterfaceConVirIP.value = null;
  hiddenTag.value = false;
  await mouseLeaveEdge(null);
};
const unmatchMaterialInterface = () => {
  let { para } = materialInterfaceData.value;
  graph?.removeCell(para.value, { slient: true });
  materialInterfaceShow.value = false;
  materialInterfaceID.value = null;
  materialInterfaceData.value = {};
  materialInterfaceList.value = [];
  materialEqInterfaceList.value = [];
  materialInterfaceConVirWg.value = null;
  materialInterfaceConVirIP.value = null;
  choiceMaterialType.value = ref(0);
};
//修改节点/连线
const changePutGraph = async para => {
  let jsonData = para;
  try {
    jsonData = jsonData.value.toJSON();
  } catch {
    jsonData = null;
    return;
  }

  if (para.type.indexOf('edge') != -1) {
    let arg = {
      id: jsonData.id || '',
      source: jsonData.source,
      target: jsonData.target,
      router: jsonData.router ? jsonData.router['name'] : '',
      strokeDasharray: jsonData.attrs.line.strokeDasharray,
      connector: jsonData.connector ? jsonData.connector['name'] : '',
      zIndex: jsonData.zIndex,
    };
    let res = await editorPutLink(ID.value, jsonData.id, arg);
    if (!(res instanceof Error)) {
      graphStore.edges = JSON.stringify(graph.getEdges());
    } else {
      graph?.removeCell(jsonData, { slient: true });

      // 判断
      JSON.parse(graphStore.edges).forEach(item => {
        if (item.id == jsonData.id) {
          graph?.addEdge(item, { slient: true });
          item = jsonData;
        }
      });
    }
  } else {
    let arg = {
      node_id: jsonData.id || '',
      id: +ID.value || '',
      height: jsonData.size.height,
      width: jsonData.size.width,
      x: jsonData.position.x,
      y: jsonData.position.y,
      zIndex: jsonData.zIndex,
    };
    let res = await editorPutNode(arg);
    if (!(res instanceof Error)) {
    } else {
      const flg = graph.canUndo();
      if (flg) {
        graph.undo();
      }
    }
  }
};

const removeCeil = async para => {
  let jsonData = para;
  jsonData = jsonData.value.toJSON();
  if (para.type.indexOf('edge') != -1) {
    let res = await deleteLink(ID.value, jsonData.id);
    if (!(res instanceof Error)) {
      graph?.removeCell(jsonData, { slient: true });
    }
  } else {
    let res = await deleteNode(ID.value, jsonData.id);
    if (!(res instanceof Error)) {
      graph?.removeCell(jsonData, { slient: true });
    }
  }
};

// 注册节点给左边侧栏
const registerNode = (arr, stencil) => {
  const node = initNode(arr);
  if (node.length) {
    for (let i = 0; i < node.length; i++) {
      const { list, groupName } = node[i];
      stencil?.load(list, groupName);
    }
  }
};
//展开详情编辑框
const editNodeFunc = async arg => {
  nodeInfo.value = arg.toJSON();
  nodeInfo.value['id'] = arg.id;
  drawer.value = true;
};

//展开流量重放选择框
const trafficReplayFunc = async arg => {
  nodeInfo.value = arg.toJSON();
  nodeInfo.value['id'] = arg.id;
  trafficDrawer.value = true;
};
const closeTrafficFunc = () => {
  trafficDrawer.value = false;
};

//展开工具执行框
const toolExecuteFunc = async arg => {
  emitter.emit('debugTool', { id: ID.value, instanceID: arg.id });
};
const closeToolExecuteFunc = () => {
  trafficDrawer.value = false;
};
const closeFunc = () => {
  drawer.value = false;
};

/**
 * 设置拖动dialog层级
 * @param item
 */
const setDragDialogZIndex = (item, DOM) => {
  let containerArr = [
    ...dialogList.value,
    ...dockerDialogList.value,
    ...dockerLogList.value,
  ];

  // 获取最大的zIndex
  let maxZIndex = Math.max(...containerArr.map(item => item.zIndex || 0));

  // 设置新的zIndex
  if (maxZIndex !== item.zIndex) {
    item.zIndex = maxZIndex + 1;
  }

  DOM.focusFunc ? DOM.focusFunc(item) : '';
};

const topDialog = item => setDragDialogZIndex(item, virtualComponent.value);

const topDockerDialog = item =>
  setDragDialogZIndex(item, dockerComponent.value);

const topDockerLogDialog = item =>
  setDragDialogZIndex(item, dockerLogComponent.value);

/**
 * 关闭虚拟机控制台
 * @param arg
 */
const closeVirtualFunc = arg => {
  let index = dialogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dialogList.value.splice(index, 1);
  }
};

/**
 * 关闭docker控制台
 * @param arg
 */
const closeDockerFunc = arg => {
  let index = dockerDialogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dockerDialogList.value.splice(index, 1);
  }
};

/**
 * 关闭日志控制台
 * @param arg
 */
const closeDockerLogFunc = arg => {
  let index = dockerLogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dockerLogList.value.splice(index, 1);
  }
};

// ================== 自定义拖拽 begin ==================
const currentDragItem = ref(null);
function onDragStart(item) {
  currentDragItem.value = item;
}

function onDrop(e) {
  e.preventDefault();
  if (!currentDragItem.value) return;
  // 获取画布坐标
  const { x, y } = graph.clientToLocal(e.clientX, e.clientY);
  // 用initNode生成节点数据
  let nodeArr = [];

  if (!currentDragItem.value?.options?.attrs) {
    nodeArr = initNode([{ name: '自定义', fileList: [currentDragItem.value] }]);
  } else {
    nodeArr = [{ groupName: '自定义', list: [currentDragItem.value.options] }];
  }

  if (nodeArr.length && nodeArr[0].list.length) {
    const nodeData = nodeArr[0].list[0];
    nodeData.x = x;
    nodeData.y = y;
    graph.addNode(nodeData);
  }
  currentDragItem.value = null;
}

/**
 * 刷新
 */
const refreshScene = () => {
  showLeft.value = !showLeft.value;
};
</script>
<style lang="scss">
.detailBox {
  height: calc(100vh - 84px);
}
.screen_content {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  height: calc(100% - 50px);
  position: relative;
  .scenes_left {
    background: #ffffff !important;
  }
  .screen_content-component {
    height: auto;
    margin-bottom: 12px;
  }

  .screen_content-bottom {
    height: calc(100% - 140px);
    position: relative;
  }

  .nodeMenu {
    color: #e1efff;
    padding: 10px;
    background: #094585;
    border-radius: 4px;
    box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16);
    li:hover {
      background-color: #0073f3;
      color: #ffffff;
      cursor: pointer;
    }

    li {
      padding: 5px 20px;
      border-radius: 4px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 11px;

      span {
        margin-left: 5px;
      }
    }
  }

  .x6-graph-scroller {
    z-index: 100;
  }

  #minimap {
    position: absolute;
    z-index: 100;
    right: 0;
    bottom: 40px;
  }

  .el-drawer__header {
    margin-bottom: 0px;
  }

  .tabContainer {
    width: 100%;
    height: 100%;
    padding: 10px;
    box-sizing: border-box;
  }

  .el-radio-button {
    border-radius: 5px;
    margin: 2px;
  }

  .el-tabs,
  .el-tab-pane {
    height: 100%;
  }

  .el-tabs__content {
    height: calc(100% - 50px) !important;
  }

  .left {
    height: 100%;
    transition: left 0.2s ease;
    position: absolute;
    box-sizing: border-box;
    background-color: #062854;
    border-radius: 5px;
    z-index: 100;
    width: 400px;
    padding: 10px;

    .leftMain {
      width: 100%;
      height: 100%;
    }
  }

  .leftIcon {
    border-radius: 50%;
    transition: width 0.2s ease;
    cursor: pointer;
    position: absolute;
    left: 390px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 999;

    .lefticonimg {
      width: 10px;
      height: 75px;
      background-image: url(@/assets/topo/right.png);
      background-size: 100% 100%;
    }

    .righticonimg {
      width: 10px;
      height: 75px;
      background-image: url(@/assets/topo/left.png);
      background-size: 100% 100%;
    }

    &:hover {
      transform: scale(1.2);
    }

    &:active {
      transform: rotate(180deg);
    }
  }

  .rightIcon {
    left: -5px;
  }

  .main {
    flex: 1;
    height: 100%;
    transition: margin 0.2s ease;
    // width: calc(100% - 445px) !important;

    .graphMain {
      width: 100% !important;
      height: 100%!important;
      margin-right: 20px;
    }

    .graphMain tspan {
      user-select: none;
    }
  }
}

.flowMain {
  background-color: white;
  display: flex;
  border-radius: 4px;
  margin-top: 10px;
  height: calc(100vh - 230px);
}

.design-left-container {
  width: 400px;
  align-items: center;
  display: inline-flex;
  flex-wrap: wrap;

  .design-left-container-item {
    flex-basis: calc((100% - 46px) / 4);
    font-size: 14px;
    height: 32px;
    line-height: 32px;
    text-align: center;
    border-radius: 4px;
    margin: 2px;
    cursor: pointer;
    color: #fff;
    background-color: #093875;
    border: 1px solid #0073f3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .design-active {
    background-color: #0073f3;
    box-shadow: none;
    border: 1px solid #0073f3;
  }
}

.scene-fold {
  // margin-left: 405px;
}

.scene-no-fold {
  //margin-left: 15px;
}

.scene-fold-left {
  left: -410px;
}

.resource-pane {
  border-radius: 12px;
  padding: 0;
  min-height: 100%;
  box-shadow: 0 2px 12px rgba(24, 144, 255, 0.04);
}
.group-bar {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-bottom: 12px;
  .group-btn {
    height: 34px;
    line-height: 34px;
    text-align: center;
    border-radius: 8px;
    background: #fff;
    color: #5b6775;
    font-size: 14px;
    cursor: pointer;
    border: 1.5px solid #e0e6ed;
    transition: all 0.18s cubic-bezier(0.4, 0, 0.2, 1);
    user-select: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    box-shadow: none;
    padding: 0 5px;
    &:hover {
      border-color: #1890ff;
      color: #fff;
      background: #1890ff;
      box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
    }
    &.active {
      background: #1890ff;
      color: #fff;
      border-color: #1890ff;
      font-weight: 600;
      box-shadow: 0 4px 16px rgba(24, 144, 255, 0.16);
    }
  }
}
.search-card {
  background: #fff;
  border-radius: 28px;
  box-shadow: 0 1px 8px rgba(24, 144, 255, 0.06);
  margin-bottom: 12px;
  border: none;
  line-height: 40px;
  .compact-search-input {
    .el-input__wrapper {
      border-radius: 28px;
      border: 2px solid #e0e6ed;
      box-shadow: none;
      transition: border-color 0.2s, box-shadow 0.2s;
      padding-left: 8px;
      min-height: 36px;
      display: flex;
      align-items: center;
    }
    .el-input__prefix {
      color: #1890ff;
      margin-right: 4px;
      display: flex;
      align-items: center;
      font-size: 16px;
      svg {
        width: 16px;
        height: 16px;
      }
    }
    input {
      background: transparent;
      border-radius: 28px;
      color: #5b6775;
      font-size: 14px;
      padding-left: 0;
      &::placeholder {
        color: #c2cbe0;
        font-size: 14px;
        letter-spacing: 0.2px;
      }
    }
    &.is-focus .el-input__wrapper {
      border-color: #1890ff;
      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.08);
      background: #fff;
    }
  }
}
.resource-collapse {
  background: transparent;
  border-top: none;
  border-bottom: none;
  height: calc(100vh - 480px);
  overflow-y: auto;
  .custom-collapse-item {
    margin-bottom: 18px;
    background: #f7faff;
    border-radius: 14px;
    box-shadow: 0 2px 12px rgba(24, 144, 255, 0.06);
    border: 1.5px solid #e0e6ed;
    overflow: hidden;
    .el-collapse-item__header {
      font-weight: 600;
      font-size: 15px;
      color: #5b6775;
      border-left: 3px solid #1890ff;
      background: #f7faff;
      border-radius: 14px 14px 0 0;
      min-height: 40px;
      align-items: center;
      display: flex;
      padding-left: 14px;
      justify-content: space-between;
      .el-collapse-item__arrow {
        margin-left: auto !important;
        order: 2;
      }
    }
    .el-collapse-item__wrap {
      background: #fff;
      border-radius: 0 0 14px 14px;
    }
    .el-collapse-item__content {
      padding: 0 10px 10px;
    }
  }
}

.resource-collapse-other {
  height: calc(100vh - 350px);
}

.resource-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 14px 0 6px 0;
}

.resource-card {
  width: 100%;
  min-width: 0;
  height: 88px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.06);
  cursor: grab;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transition: box-shadow 0.2s, border 0.2s;
  border: 1.5px solid #e0e6ed;
  &:hover {
    border-color: #1890ff;
    //background: #f0f8ff;
    box-shadow: 0 4px 16px rgba(24, 144, 255, 0.12);
  }

  .el-tooltip__trigger {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  .icon-bg {
    width: 45px;
    height: 54px;
    background: #fff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 1px 4px rgba(24, 144, 255, 0.04);
    //border: 1.5px solid #c9d5e2;
  }
  .resource-icon {
    width: 45px;
    height: 54px;
    object-fit: contain;
  }
  .resource-name {
    display: inline-block;
    width: 68px;
    font-size: 14px;
    color: #5b6775;
    text-align: center;
    word-break: break-all;
    line-height: 1;
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    position: relative;
  }
}

// 响应式兼容
@media (max-width: 1400px) {
  .group-bar {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (max-width: 900px) {
  .group-bar {
    grid-template-columns: repeat(4, 1fr);
  }
  .resource-card {
    //height: 56px;

    .resource-name {
      font-size: 12px;
      max-width: 60px;
    }
  }
}
</style>
